#ifndef _NetfilterA_H_
#define _NetfilterA_H_
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <string>
#include <netinet/in.h>
#include <linux/types.h>
#include <linux/netfilter.h>		/* for NF_ACCEPT */
#include <linux/ip.h>
#include <linux/tcp.h>
#include <linux/udp.h>
#include <errno.h>
#include <future>
#include <chrono>
#include <iostream>
#include <map>
#include <libnetfilter_queue/libnetfilter_queue.h>
#include "../Analyzer/AnalyzerFactory.h"
#include "../Analyzer/TemplateAnalyzer.h"
#include "../Analyzer/IEC104Analyzer.h"

#define SWITCH_ON 1
#define SWITCH_OFF 0
using namespace std;

static int cb(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg,struct nfq_data *nfa, void *data);
static int hash_cb(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg,struct nfq_data *nfa, void *data);
static u_int32_t print_pkt (struct nfq_data *tb);
static u_int32_t hash_print_pkt (struct nfq_data *tb);

static bool switch_craft;
static string result;
static map<string,string> param;

class NetfilterA{

public:
	NetfilterA(const string &operation,map<string,string> _param);
	~NetfilterA();
	string filter_start();

private:
	struct nfq_handle *h;
	struct nfq_q_handle *qh;
	int fd;
	int rv;
	char buf[4096] __attribute__ ((aligned));
	void initialize(const string &type);
};

NetfilterA::NetfilterA(const string &type,map<string,string> _param)
{
	param=_param;
	this->initialize(type);
}

NetfilterA::~NetfilterA()
{
	printf("unbinding from queue 0\n");
	nfq_destroy_queue(qh);
	printf("closing library handle\n");
	nfq_close(h);
}

void NetfilterA::initialize(const string &type)
{
	printf("opening library handle\n");
	this->h = nfq_open();
	if (!h) {
		fprintf(stderr, "error during nfq_open()\n");
		exit(1);
	}

	printf("unbinding existing nf_queue handler for AF_INET (if any)\n");
	if (nfq_unbind_pf(h, AF_INET) < 0) {
		fprintf(stderr, "error during nfq_unbind_pf()\n");
		exit(1);
	}

	printf("binding nfnetlink_queue as nf_queue handler for AF_INET\n");
	if (nfq_bind_pf(h, AF_INET) < 0) {
		fprintf(stderr, "error during nfq_bind_pf()\n");
		exit(1);
	}

	printf("binding this socket to queue '0'\n");
	if(type=="hash"){
		qh = nfq_create_queue(h,  0, &hash_cb, NULL);
	}
	else {
		qh = nfq_create_queue(h,  0, &cb, NULL);
	}
	if (!qh) {
		fprintf(stderr, "error during nfq_create_queue()\n");
		exit(1);
	}

	printf("setting copy_packet mode\n");
	if (nfq_set_mode(qh, NFQNL_COPY_PACKET, 0xffff) < 0) {
		fprintf(stderr, "can't set packet_copy mode\n");
		exit(1);
	}
	fd = nfq_fd(h);
	switch_craft=SWITCH_ON;
}

static int cb(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg,struct nfq_data *nfa, void *data)
{
	u_int32_t id = print_pkt(nfa);
	printf("entering callback\n");
	return nfq_set_verdict(qh, id, NF_ACCEPT, 0, NULL);
}

static int hash_cb(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg,struct nfq_data *nfa, void *data)
{
	u_int32_t id = hash_print_pkt(nfa);
	printf("entering callback\n");
	return nfq_set_verdict(qh, id, NF_ACCEPT, 0, NULL);
}

static u_int32_t hash_print_pkt(struct nfq_data *tb)
{
	int id = 0;
	struct nfqnl_msg_packet_hdr *ph;
	int ret;
	unsigned char *data;

	ph = nfq_get_msg_packet_hdr(tb);
	if (ph) {
		id = ntohl(ph->packet_id);
		printf("hw_protocol=0x%04x hook=%u id=%u ",
			ntohs(ph->hw_protocol), ph->hook, id);
	}
	cout<<endl;
	ret = nfq_get_payload(tb, &data);
	AnalyzerFactory factory;
	TemplateAnalyzer *analyzer=factory.create_analyzer("IEC104",data,ret);
	int startlen=0;
	analyzer->analyze_appLayer(data,startlen);
	cout<<analyzer->get_tran_type()<<endl;
	if (ret >= 0){
		printf("payload_len=%d ", ret);
		switch_craft=SWITCH_OFF;
		result="hash print_pkt";
	}
	fputc('\n', stdout);
	return id;
}

static u_int32_t  print_pkt (struct nfq_data *tb)
{
	int id = 0;
	struct nfqnl_msg_packet_hdr *ph;
	struct nfqnl_msg_packet_hw *hwph;
	u_int32_t mark,ifi; 
	int ret;
	unsigned char *data;

	ph = nfq_get_msg_packet_hdr(tb);
	if (ph) {
		id = ntohl(ph->packet_id);
		printf("hw_protocol=0x%04x hook=%u id=%u ",
			ntohs(ph->hw_protocol), ph->hook, id);
	}

	hwph = nfq_get_packet_hw(tb);
	if (hwph) {
		int i, hlen = ntohs(hwph->hw_addrlen);

		printf("hw_src_addr=");
		for (i = 0; i < hlen-1; i++)
			printf("%02x:", hwph->hw_addr[i]);
		printf("%02x ", hwph->hw_addr[hlen-1]);
	}

	mark = nfq_get_nfmark(tb);
	if (mark)
		printf("mark=%u ", mark);

	ifi = nfq_get_indev(tb);
	if (ifi)
		printf("indev=%u ", ifi);

	ifi = nfq_get_outdev(tb);
	if (ifi)
		printf("outdev=%u ", ifi);
	ifi = nfq_get_physindev(tb);
	if (ifi)
		printf("physindev=%u ", ifi);

	ifi = nfq_get_physoutdev(tb);
	if (ifi)
		printf("physoutdev=%u ", ifi);

	ret = nfq_get_payload(tb, &data);
	if (ret >= 0)
		printf("payload_len=%d ", ret);
	if(ret>1000){
		cout<<"payload_len>1000!"<<endl;
		switch_craft=SWITCH_OFF;
	}
	fputc('\n', stdout);

	return id;
}

string NetfilterA::filter_start()
{
	for (;;) {
		if(!switch_craft)
			break;
		if ((rv = recv(fd, buf, sizeof(buf), 0)) >= 0) {
			printf("pkt received\n");
			nfq_handle_packet(h, buf, rv);
			continue;
		}
		/* if your application is too slow to digest the packets that
		 * are sent from kernel-space, the socket buffer that we use
		 * to enqueue packets may fill up returning ENOBUFS. Depending
		 * on your application, this error may be ignored. Please, see
		 * the doxygen documentation of this library on how to improve
		 * this situation.
		 */
		if (rv < 0 && errno == ENOBUFS) {
			printf("losing packets!\n");
			continue;
		}
		perror("recv failed");
		break;
	}
	return result;
}

#ifdef INSANE
	/* normally, applications SHOULD NOT issue this command, since
	 * it detaches other programs/sockets from AF_INET, too ! */
	printf("unbinding from AF_INET\n");
	nfq_unbind_pf(h, AF_INET);
#endif

#endif 